package ru.ifmo.rybakov.muclumon.analysis2;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import ru.ifmo.rybakov.muclumon.NumericValue;
import ru.ifmo.rybakov.muclumon.Property;
import ru.ifmo.rybakov.muclumon.Task;

public class PropertyCorellationAnalyzer {
	
	private static class CorellationData {
		/**
		 * Middle value
		 */
		double m;
		
		/**
		 * Сумма квадратов отклонений
		 */
		double S;
		
		/**
		 * Стандартное отконение
		 */
		double s;
		
		public CorellationData(double v[]) {
			m = 0.0;
			for (int i = 0; i < v.length; i++) {
				m += v[i];
			}
			m /= v.length;
			
			S = 0.0;
			for (int i = 0; i < v.length; i++) {
				S += Math.pow(v[i] - m, 2);
			}
			
			s = Math.sqrt(S / (v.length - 1));
		}
	}
	
	private Map<Property, Integer> positions = new HashMap<Property, Integer>();
	private Map<Integer, Property> properties = new HashMap<Integer, Property>();
	
	private double[][] r;
	private double[] rY;
	
	// Collection<Property> 
	public PropertyCorellationAnalyzer(Collection<Property> properties, TaskCluster cluster, Map<Task, Double> y) {
		// order properties in an input vector
		Integer i = 0;
		for (Property p : properties) {
			positions.put(p, i);
			this.properties.put(i, p);
			i++;
		}
		
		
		Map<Property, CorellationData> corellation = new HashMap<Property, CorellationData>();
		
		// now calculate corellation coefficients
		for (Property p : properties) {
			double v[] = new double[cluster.getTasks().size()];
			i = 0;
			for (Task task : cluster.getTasks()) {
				System.out.println(task.getId());
				v[i] = ((NumericValue) task.getSingleValue(p)).getValue();
				i++;
			}
			corellation.put(p, new CorellationData(v));
		}
		
		r = new double[properties.size()][properties.size()];
		
		for (Property p1 : properties) {
			for (Property p2 : properties) {
				double r12;
				if (p1.equals(p2)) {
					r12 = 1.0;
				} else {
					CorellationData cd1 = corellation.get(p1);
					CorellationData cd2 = corellation.get(p2);
					
					double S12 = 0.0; // сумма произведений отклонений
					for (Task task : cluster.getTasks()) {
						double v1 = ((NumericValue) task.getSingleValue(p1)).getValue();
						double v2 = ((NumericValue) task.getSingleValue(p2)).getValue();
						S12 += (v1 - cd1.m) * (v2 - cd2.m);
					}
					double s12 = S12 / (cluster.getTasks().size() - 1); // ковариация
					r12 = s12 / (cd1.s * cd2.s);
				}
				
				System.out.println(p1.getName() + "::" + p2.getName() + "=" + r12);
				r[positions.get(p1)][positions.get(p2)] = r12;
			}
		}
		
		// find correlation between input values and output
		rY = new double[properties.size()];
		
		Collection<Double> values = y.values();
		double yv[] = new double[values.size()];
		int k = 0;
		for (Double d : values) {
			yv[k] = d;
			k++;
		}
		CorellationData yCD = new CorellationData(yv);
		for (Property p : properties) {
			CorellationData xCD = corellation.get(p);
			double Sxy = 0.0; // сумма произведений отклонений
			for (Task task : cluster.getTasks()) {
				double x = ((NumericValue) task.getSingleValue(p)).getValue();
				Sxy += (x - xCD.m) * (y.get(task) - yCD.m);
			}
			double sXY = Sxy / (cluster.getTasks().size() - 1); // ковариация
			double rXY = sXY / (xCD.s * yCD.s);
			rY[positions.get(p)] = rXY;
			System.out.println(p.getName() + "::y=" + rXY);
		}
	}
	
	/**
	 * Returns correlation coefficient between two properties
	 * @param p1 First property
	 * @param p2 Second property
	 * @return Coefficient
	 */
	public double getCoefficient(Property p1, Property p2) {
		return r[positions.get(p1)][positions.get(p2)];
	}
	
	/**
	 * Returns correlation coefficient between property and output value
	 * @param p Property
	 * @return Coefficient
	 */
	public double getCoefficient(Property p) {
		return rY[positions.get(p)];
	}
}
